import classNames from "classnames";
import React, { FC, useEffect, useState } from "react";
import { MinusOutline } from "./minus-outline";
import { AddOutline } from "./add-outline";
import { bound } from "../utils/bound";
import Input, { InputProps } from "../input";

const classPrefix = `bbt-stepper`;

type ValueProps = {
  allowEmpty: true;
  value?: number | null;
  defaultValue?: number | null;
  onChange?: (value: number | null) => void;
};

type ValuePropsWithNull = {
  allowEmpty?: false;
  value?: number;
  defaultValue?: number;
  onChange?: (value: number) => void;
};

export type StepperProps = Pick<InputProps, "onFocus" | "onBlur"> &
  (ValuePropsWithNull | ValueProps) & {
    min?: number;
    max?: number;
    step?: number;
    digits?: number;
    disabled?: boolean;
    inputReadOnly?: boolean;
    style?: React.CSSProperties & Partial<Record<string | never, string>>;
  };

const defaultProps = {
  defaultValue: 0,
  step: 1,
  disabled: false,
  allowEmpty: false,
};

export const Stepper: FC<StepperProps> = (p) => {
  const props = { ...defaultProps, ...p };
  const { disabled, step, max, min, inputReadOnly } = props;

  const [value, setValue] = useState<number | null>(props.value as any);
  const [inputValue, setInputValue] = useState(() =>
    convertValueToText(value, props.digits)
  );
  function setValueWithCheck(v: number) {
    if (isNaN(v)) return;
    let target = bound(v, props.min, props.max);
    if (props.digits !== undefined) {
      target = parseFloat(Number(target).toFixed(props.digits));
    }
    setValue(target);
  }

  const [hasFocus, setHasFocus] = useState(false);

  useEffect(() => {
    if (!hasFocus) {
      setInputValue(convertValueToText(value, props.digits));
    }
  }, [hasFocus]);

  useEffect(() => {
    if (!hasFocus) {
      setInputValue(convertValueToText(value, props.digits));
    }
  }, [value, props.digits]);

  const handleInputChange = (v: string) => {
    console.log("change", v);
    setInputValue(v);
    const value = convertTextToValue(v);
    if (value === null) {
      if (props.allowEmpty) {
        setValue(null);
      } else {
        setValue(props.defaultValue);
      }
    } else {
      setValueWithCheck(value);
    }
  };

  const handleMinus = () => {
    const v = value || 0;
    setValueWithCheck(v - 0 - step);
  };

  const handlePlus = () => {
    const v = value || 0;
    setValueWithCheck(v - 0 + step);
  };

  const minusDisabled = () => {
    if (disabled) return true;
    if (value === null) return false;
    if (min !== undefined) {
      return value <= min;
    }
    return false;
  };

  const plusDisabled = () => {
    if (disabled) return true;
    if (value === null) return false;
    if (max !== undefined) {
      return value >= max;
    }
    return false;
  };

  return (
    <div
      className={classNames(classPrefix, {
        [`${classPrefix}-active`]: hasFocus,
      })}
    >
      <button
        className={`${classPrefix}-minus`}
        onClick={handleMinus}
        disabled={minusDisabled()}
        aria-label="decrease"
      >
        <MinusOutline />
      </button>
      <div className={`${classPrefix}-middle`}>
        <Input
          className={`${classPrefix}-input`}
          onFocus={(e) => {
            setHasFocus(true);
            props.onFocus?.(e);
          }}
          value={inputValue}
          onChange={(val) => {
            disabled || handleInputChange(val);
          }}
          disabled={disabled}
          onBlur={(e) => {
            setHasFocus(false);
            props.onBlur?.(e);
          }}
          readOnly={inputReadOnly}
          role="spinbutton"
          aria-valuenow={Number(inputValue)}
          aria-valuemax={max}
          aria-valuemin={min}
        />
      </div>
      <button
        className={`${classPrefix}-plus`}
        onClick={handlePlus}
        disabled={plusDisabled()}
        aria-label="increase"
      >
        <AddOutline />
      </button>
    </div>
  );
};

function convertValueToText(value: number | null, digits?: number) {
  if (value === null || value === undefined) return "";
  if (digits !== undefined) {
    return Number(value).toFixed(digits);
  } else {
    return value.toString();
  }
}

function convertTextToValue(text: string) {
  if (text === "") return null;
  return parseFloat(text);
}
